package net.gdface.bean.jdk.factory;

import java.util.Arrays;
import java.util.List;
import java.util.Set;

import com.google.common.base.Function;
import com.google.common.base.Optional;
import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;

import gu.sql2java.BaseRow;
import gu.sql2java.RowMetaData;
import net.gdface.annotations.ActiveOnClass;
import net.gdface.bean.exception.BeanPropertyRuntimeException;
import net.gdface.bean.jdk.descriptor.BaseRowPropertyDescriptor;
import net.gdface.cache.FunctionCached;

import static com.google.common.base.Preconditions.checkArgument;
import static net.gdface.annotations.ActiveOnClassSupport.getClassesInActiveOnClass;

/**
 * @author guyadong
 * @since 2.7.0
 *
 */
@ActiveOnClass(BaseRow.class)
public class BaseRowDescriptorFactory implements PropertyDescriptorFactory {
	private volatile Class<? extends BaseRow> activeOnClass;
	@SuppressWarnings("unchecked")
	private Class<? extends BaseRow> getClassForActivation() {
		if (null == activeOnClass) {
			synchronized (this) {
				if (null == activeOnClass) {
					Set<Class<?>> classes = getClassesInActiveOnClass(getClass());
					checkArgument(!classes.isEmpty(),
							"@ActiveOnClass not define OR not define class in @ActiveOnClass on %s",
							getClass().getName());
					Class<?> first = classes.iterator().next();
					checkArgument(BaseRow.class.isAssignableFrom(first),
							"BaseRow or subclass required in @ActiveOnClass on %s", getClass().getName());
					activeOnClass = (Class<? extends BaseRow>) first;
				}

			}
		}
		return activeOnClass;
	}

	private final FunctionCached<List<?>, Optional<PropertyDescriptor>> descriptorCached = FunctionCached.of(new Function<List<?>,Optional<PropertyDescriptor>>(){

		@Override
		public Optional<PropertyDescriptor> apply(List<?> input) {
			if(input.get(1) instanceof String){
				RowMetaData metaData = ((RowMetaData)input.get(0));
				String name = (String)input.get(1);
				if(metaData.columnIDOf(name) >= 0){
					try {
						return Optional.<PropertyDescriptor>of(createDescriptor(metaData,name));
					} catch (IntrospectionException e) {
						throw new BeanPropertyRuntimeException(e);
					}
				}
			}
			return Optional.absent();
		}});
	@Override
	public PropertyDescriptor descriptorOf(Object bean, String name) throws IntrospectionException {
		try {
			if(getClassForActivation().isInstance(bean) ){
				return descriptorCached.get(Arrays.asList(getClassForActivation().cast(bean).fetchMetaData(),name)).orNull();
			}
			return null;
		} catch (BeanPropertyRuntimeException e) {
			if(e.getCause() instanceof IntrospectionException) {
				throw (IntrospectionException)e.getCause();
			}
			throw e;
		}
	}

	protected PropertyDescriptor createDescriptor(RowMetaData metaData,String name) throws IntrospectionException {
		return new BaseRowPropertyDescriptor(metaData,name);
	}
}
